package com.mem.barrier;

import java.util.HashSet;

/**
 *   在高并发模型中，无是面对物理机SMP系统模型，还是面对像JVM的虚拟机多线程并发内存模型，指令重排(编译器、运行时)和内存屏障都是非常重要的概念，因此，搞清楚这些概念和原理很重要。否则，你很难搞清楚哪些操作是在并发先绝对安全的？哪些是相对安全的？哪些并发同步手段性能最低？valotile的二层语义分别是什么？等等。
 *       本来打算自己写一篇有关JVM内存模型的博文，后来整理资料的时候偶然发现一篇很好的相关文章(出自美团点评团队)，个人感觉这篇文章写得比较全面，最起码概念层的东西讲清楚了，遂转载给大家。原文地址：http://tech.meituan.com/java-memory-reordering.html
 * 一、什么是重排序
 *       很容易想到这段代码的运行结果可能为(1,0)、(0,1)或(1,1)，因为线程one可以在线程two开始之前就执行完了，也有可能反之，甚至有可能二者的指令是同时或交替执行的。
 *       然而，这段代码的执行结果也可能是(0,0). 因为，在实际运行时，代码指令可能并不是严格按照代码语句顺序执行的。得到(0,0)结果的语句执行过程，如下图所示。值得注意的是，a=1和x=b这两个语句的赋值操作的顺序被颠倒了，或者说，发生了指令“重排序”(reordering)。（事实上，输出了这一结果，并不代表一定发生了指令重排序，内存可见性问题也会导致这样的输出，详见后文）
 *      对重排序现象不太了解的开发者可能会对这种现象感到吃惊，但是，笔者开发环境下做的一个小实验证实了这一结果。
 *      实验代码是构造一个循环，反复执行上面的实例代码，直到出现a=0且b=0的输出为止。实验结果说明，循环执行到第13830次时输出了(0,0)。
 *
 *      大多数现代微处理器都会采用将指令乱序执行（out-of-order execution，简称OoOE或OOE）的方法，在条件允许的情况下，直接运行当前有能力立即执行的后续指令，避开获取下一条指令所需数据时造成的等待3。通过乱序执行的技术，处理器可以大大提高执行效率。
 *      除了处理器，常见的Java运行时环境的JIT编译器也会做指令重排序操作，即生成的机器指令与字节码指令顺序不一致。
 *
 * 二、as-if-serial语义
 *      As-if-serial语义的意思是，所有的动作(Action)都可以为了优化而被重排序，但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。
 *      比如，为了保证这一语义，重排序不会发生在有数据依赖的操作之中。
 * 三、内存访问重排序与内存可见性
 *       计算机系统中，为了尽可能地避免处理器访问主内存的时间开销，处理器大多会利用缓存(cache)以提高性能。其模型如下图所示。
 *
 *
 *
 *      在这种模型下会存在一个现象，即缓存中的数据与主内存的数据并不是实时同步的，各CPU（或CPU核心）间缓存的数据也不是实时同步的。这导致在同一个时间点，各CPU所看到同一内存地址的数据的值可能是不一致的。从程序的视角来看，就是在同一个时间点，各个线程所看到的共享变量的值可能是不一致的。
 *      有的观点会将这种现象也视为重排序的一种，命名为“内存系统重排序”。因为这种内存可见性问题造成的结果就好像是内存访问指令发生了重排序一样。
 *      这种内存可见性问题也会导致章节一中示例代码即便在没有发生指令重排序的情况下的执行结果也还是(0, 0)。
 *
 * 四、内存访问重排序与Java内存模型
 *       Java的目标是成为一门平台无关性的语言，即Write once, run anywhere. 但是不同硬件环境下指令重排序的规则不尽相同。例如，x86下运行正常的Java程序在IA64下就可能得到非预期的运行结果。为此，JSR-1337制定了Java内存模型(Java Memory Model, JMM)，旨在提供一个统一的可参考的规范，屏蔽平台差异性。从Java 5开始，Java内存模型成为Java语言规范的一部分。
 *      根据Java内存模型中的规定，可以总结出以下几条happens-before规则。Happens-before的前后两个操作不会被重排序且后者对前者的内存可见。
 *
 * 程序次序法则：线程中的每个动作A都happens-before于该线程中的每一个动作B，其中，在程序中，所有的动作B都能出现在A之后。
 * 监视器锁法则：对一个监视器锁的解锁 happens-before于每一个后续对同一监视器锁的加锁。
 * volatile变量法则：对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。
 * 线程启动法则：在一个线程里，对Thread.start的调用会happens-before于每个启动线程的动作。
 * 线程终结法则：线程中的任何动作都happens-before于其他线程检测到这个线程已经终结、或者从Thread.join调用中成功返回，或Thread.isAlive返回false。
 * 中断法则：一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断。
 * 终结法则：一个对象的构造函数的结束happens-before于这个对象finalizer的开始。
 * 传递性：如果A happens-before于B，且B happens-before于C，则A happens-before于C
 *       Happens-before关系只是对Java内存模型的一种近似性的描述，它并不够严谨，但便于日常程序开发参考使用，关于更严谨的Java内存模型的定义和描述，请阅读JSR-133原文或Java语言规范章节17.4。
 *       除此之外，Java内存模型对volatile和final的语义做了扩展。对volatile语义的扩展保证了volatile变量在一些情况下不会重排序，volatile的64位变量double和long的读取和赋值操作都是原子的。对final语义的扩展保证一个对象的构建方法结束前，所有final成员变量都必须完成初始化（的前提是没有this引用溢出）。
 *       Java内存模型关于重排序的规定，总结后如下表所示。
 *       表中“第二项操作”的含义是指，第一项操作之后的所有指定操作。如，普通读不能与其之后的所有volatile写重排序。另外，JMM也规定了上述volatile和同步块的规则尽适用于存在多线程访问的情景。例如，若编译器（这里的编译器也包括JIT，下同）证明了一个volatile变量只能被单线程访问，那么就可能会把它做为普通变量来处理。
 *       留白的单元格代表允许在不违反Java基本语义的情况下重排序。例如，编译器不会对对同一内存地址的读和写操作重排序，但是允许对不同地址的读和写操作重排序。
 *       除此之外，为了保证final的新增语义。JSR-133对于final变量的重排序也做了限制。
 * 构建方法内部的final成员变量的存储，并且，假如final成员变量本身是一个引用的话，这个final成员变量可以引用到的一切存储操作，都不能与构建方法外的将当期构建对象赋值于多线程共享变量的存储操作重排序。例如对于如下语句
 * x.finalField = v; ... ;构建方法边界sharedRef = x;
 * v.afield = 1; x.finalField = v; ... ; 构建方法边界sharedRef = x;
 * 这两条语句中，构建方法边界前后的指令都不能重排序。
 * 初始读取共享对象与初始读取该共享对象的final成员变量之间不能重排序。例如对于如下语句
 * x = sharedRef; ... ; i = x.finalField;
 * 前后两句语句之间不会发生重排序。由于这两句语句有数据依赖关系，编译器本身就不会对它们重排序，但确实有一些处理器会对这种情况重排序，因此特别制定了这一规则。
 * 五、内存屏障
 *       内存屏障（Memory Barrier，或有时叫做内存栅栏，Memory Fence）是一种CPU指令，用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
 *       内存屏障可以被分为以下几种类型
 * LoadLoad屏障：对于这样的语句Load1; LoadLoad; Load2，在Load2及后续读取操作要读取的数据被访问前，保证Load1要读取的数据被读取完毕。
 * StoreStore屏障：对于这样的语句Store1; StoreStore; Store2，在Store2及后续写入操作执行前，保证Store1的写入操作对其它处理器可见。
 * LoadStore屏障：对于这样的语句Load1; LoadStore; Store2，在Store2及后续写入操作被刷出前，保证Load1要读取的数据被读取完毕。
 * StoreLoad屏障：对于这样的语句Store1; StoreLoad; Load2，在Load2及后续所有读取操作执行前，保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。        在大多数处理器的实现中，这个屏障是个万能屏障，兼具其它三种内存屏障的功能。
 *        有的处理器的重排序规则较严，无需内存屏障也能很好的工作，Java编译器会在这种情况下不放置内存屏障。
 *        为了实现上一章中讨论的JSR-133的规定，Java编译器会这样使用内存屏障。
 */
public class MemoryBarrier {

    private static  int a = 0;
    private static  int b = 0;
    private static  int x = 0;
    private static  int y = 0;

    public static void main(String[] args) throws InterruptedException {
        HashSet<String> stringHashset = new HashSet<>();
        for (int i = 0; i < 10000; i++) {
            a = 0;
            b = 0;
            x = 0;
            y = 0;
            Thread thread1 = new Thread(() -> {
                try {
                    Thread.sleep(1L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                b = 2;
                y = a;
            });

            Thread thread2 = new Thread(() -> {
                try {
                    Thread.sleep(1L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                a = 1;
                x = b;
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();

            stringHashset.add(String.format("{x=%d, y= %d}", x, y));
        }
        System.out.println(String.join("-------", stringHashset));
    }
}
